package com.minio.learning.client;

import com.minio.learning.config.MinioConfig;
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * MinIO 客户端封装类
 * 
 * 为什么要封装？
 * 1. 简化 MinIO 原生 API 的使用
 * 2. 统一异常处理和日志记录
 * 3. 提供更友好的方法签名
 * 4. 便于在项目中复用
 * 5. 可以添加业务相关的逻辑（如权限检查、日志记录等）
 * 
 * 使用方式：
 * MinioClientWrapper client = new MinioClientWrapper();
 * client.createBucket("my-bucket");
 */
public class MinioClientWrapper {
    
    private static final Logger logger = LoggerFactory.getLogger(MinioClientWrapper.class);
    
    /**
     * MinIO 原生客户端
     * 这是 MinIO Java SDK 提供的核心类
     */
    private final MinioClient minioClient;
    
    /**
     * 构造函数 - 使用默认配置初始化
     * 
     * 为什么在构造函数中初始化？
     * - 确保客户端在对象创建时就可用
     * - 便于依赖注入（Spring Boot 中）
     */
    public MinioClientWrapper() {
        this.minioClient = MinioClient.builder()
                .endpoint(MinioConfig.getEndpoint())
                .credentials(MinioConfig.getAccessKey(), MinioConfig.getSecretKey())
                .build();
        
        logger.info("✅ MinIO 客户端初始化成功！连接到: {}", MinioConfig.getEndpoint());
    }
    
    /**
     * 构造函数 - 自定义配置
     * 
     * 使用场景：
     * - 需要连接到不同的 MinIO 服务器
     * - 单元测试中使用模拟配置
     */
    public MinioClientWrapper(String endpoint, String accessKey, String secretKey) {
        this.minioClient = MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
        
        logger.info("✅ MinIO 客户端初始化成功！连接到: {}", endpoint);
    }
    
    // ==================== 桶（Bucket）操作 ====================
    
    /**
     * 创建桶
     * 
     * 桶是什么？
     * - 类似文件系统中的"顶级目录"
     * - 用于组织和管理对象
     * - 桶名称必须全局唯一（在整个 MinIO 实例中）
     * 
     * 命名规则：
     * - 只能包含小写字母、数字、连字符
     * - 长度 3-63 个字符
     * - 不能以连字符开头或结尾
     * - 不能包含两个连续的连字符
     * 
     * @param bucketName 桶名称
     * @return 是否创建成功
     */
    public boolean createBucket(String bucketName) {
        try {
            // 检查桶是否已存在
            boolean exists = minioClient.bucketExists(
                    BucketExistsArgs.builder()
                            .bucket(bucketName)
                            .build()
            );
            
            if (exists) {
                logger.warn("⚠️  桶 '{}' 已存在", bucketName);
                return true;
            }
            
            // 创建桶
            minioClient.makeBucket(
                    MakeBucketArgs.builder()
                            .bucket(bucketName)
                            .build()
            );
            
            logger.info("✅ 桶 '{}' 创建成功！", bucketName);
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 创建桶失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 检查桶是否存在
     * 
     * 使用场景：
     * - 创建桶前检查
     * - 上传文件前验证桶存在
     * 
     * @param bucketName 桶名称
     * @return 是否存在
     */
    public boolean bucketExists(String bucketName) {
        try {
            return minioClient.bucketExists(
                    BucketExistsArgs.builder()
                            .bucket(bucketName)
                            .build()
            );
        } catch (Exception e) {
            logger.error("❌ 检查桶是否存在失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 列出所有桶
     * 
     * 使用场景：
     * - 查看有哪些桶可用
     * - 管理和维护桶
     * 
     * @return 桶名称列表
     */
    public List<String> listBuckets() {
        List<String> bucketNames = new ArrayList<>();
        
        try {
            List<Bucket> buckets = minioClient.listBuckets();
            
            logger.info("\n📦 共有 {} 个桶：", buckets.size());
            for (Bucket bucket : buckets) {
                bucketNames.add(bucket.name());
                logger.info("  - {}", bucket.name());
            }
            
        } catch (Exception e) {
            logger.error("❌ 列出桶失败: {}", e.getMessage());
        }
        
        return bucketNames;
    }
    
    /**
     * 删除桶
     * 
     * 注意：
     * - 只能删除空桶（桶内没有对象）
     * - 删除前需要先删除桶内所有对象
     * 
     * @param bucketName 桶名称
     * @return 是否删除成功
     */
    public boolean deleteBucket(String bucketName) {
        try {
            minioClient.removeBucket(
                    RemoveBucketArgs.builder()
                            .bucket(bucketName)
                            .build()
            );
            
            logger.info("✅ 桶 '{}' 删除成功！", bucketName);
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 删除桶失败: {}", e.getMessage());
            logger.info("💡 提示：确保桶是空的（没有对象）");
            return false;
        }
    }
    
    // ==================== 对象（Object）操作 ====================
    
    /**
     * 上传文件到 MinIO
     * 
     * 这是最常用的功能！
     * 
     * 参数说明：
     * @param bucketName 桶名称（文件存储到哪个桶）
     * @param objectName 对象名称（存储后的文件名，可以包含路径）
     * @param filePath 本地文件路径
     * @return 是否上传成功
     * 
     * 为什么需要 objectName？
     * - 可以重命名文件
     * - 可以模拟文件夹结构，如 "images/2024/photo.jpg"
     * - 便于文件分类和管理
     * 
     * 示例：
     * uploadFile("my-bucket", "photos/avatar.jpg", "C:/Users/me/avatar.jpg")
     * 
     * 底层原理：
     * - 文件会被分块上传（大文件自动分片）
     * - 支持断点续传
     * - 上传完成后计算 ETag（文件唯一标识）
     */
    public boolean uploadFile(String bucketName, String objectName, String filePath) {
        try {
            File file = new File(filePath);
            
            // 检查文件是否存在
            if (!file.exists()) {
                logger.error("❌ 文件不存在: {}", filePath);
                return false;
            }
            
            // 上传文件
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(filePath)
                            .build()
            );
            
            logger.info("✅ 文件上传成功！");
            logger.info("   桶: {}", bucketName);
            logger.info("   对象名: {}", objectName);
            logger.info("   大小: {} 字节", file.length());
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 上传文件失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 上传文件（指定内容类型）
     * 
     * 什么是内容类型（Content-Type）？
     * - 描述文件的 MIME 类型
     * - 浏览器根据此类型决定如何处理文件
     * 
     * 常见类型：
     * - image/jpeg, image/png - 图片
     * - video/mp4 - 视频
     * - application/pdf - PDF
     * - text/plain - 文本
     * - application/json - JSON
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param filePath 本地文件路径
     * @param contentType 内容类型
     * @return 是否上传成功
     */
    public boolean uploadFile(String bucketName, String objectName, 
                             String filePath, String contentType) {
        try {
            File file = new File(filePath);
            
            if (!file.exists()) {
                logger.error("❌ 文件不存在: {}", filePath);
                return false;
            }
            
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(filePath)
                            .contentType(contentType)
                            .build()
            );
            
            logger.info("✅ 文件上传成功！");
            logger.info("   桶: {}", bucketName);
            logger.info("   对象名: {}", objectName);
            logger.info("   类型: {}", contentType);
            logger.info("   大小: {} 字节", file.length());
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 上传文件失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 上传字节数据（从内存）
     * 
     * 什么时候用这个？
     * - 上传动态生成的数据（如图片、文本）
     * - 不需要先保存到本地文件
     * - 处理流式数据
     * 
     * 使用场景：
     * - 图片处理后直接上传
     * - 生成的报表文件
     * - API 返回的数据
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param data 字节数据
     * @param contentType 内容类型
     * @return 是否上传成功
     */
    public boolean uploadBytes(String bucketName, String objectName, 
                              byte[] data, String contentType) {
        try {
            ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
            
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, data.length, -1)
                            .contentType(contentType)
                            .build()
            );
            
            logger.info("✅ 数据上传成功！");
            logger.info("   桶: {}", bucketName);
            logger.info("   对象名: {}", objectName);
            logger.info("   大小: {} 字节", data.length);
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 上传数据失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 从 MinIO 下载文件
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param filePath 保存到本地的文件路径
     * @return 是否下载成功
     * 
     * 注意：
     * - 如果目标文件已存在，会被覆盖
     * - 会自动创建目标目录
     */
    public boolean downloadFile(String bucketName, String objectName, String filePath) {
        try {
            // 确保目标目录存在
            File file = new File(filePath);
            File parentDir = file.getParentFile();
            if (parentDir != null && !parentDir.exists()) {
                parentDir.mkdirs();
            }
            
            // 下载文件
            minioClient.downloadObject(
                    DownloadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(filePath)
                            .build()
            );
            
            logger.info("✅ 文件下载成功！");
            logger.info("   保存到: {}", filePath);
            logger.info("   大小: {} 字节", file.length());
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 下载文件失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 从 MinIO 下载数据到内存
     * 
     * 什么时候用这个？
     * - 直接处理数据，不需要保存到文件
     * - 读取配置文件
     * - 图片处理
     * - 内容预览
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 字节数据，失败返回 null
     */
    public byte[] downloadBytes(String bucketName, String objectName) {
        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build()
        )) {
            byte[] data = stream.readAllBytes();
            logger.info("✅ 数据下载成功！大小: {} 字节", data.length);
            return data;
            
        } catch (Exception e) {
            logger.error("❌ 下载数据失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 列出桶中的对象
     * 
     * @param bucketName 桶名称
     * @return 对象名称列表
     * 
     * 使用场景：
     * - 查看桶中有哪些文件
     * - 批量处理文件
     * - 文件管理界面
     */
    public List<String> listObjects(String bucketName) {
        return listObjects(bucketName, "");
    }
    
    /**
     * 列出桶中的对象（带前缀过滤）
     * 
     * 什么是前缀？
     * - 对象名称的开头部分
     * - 可以模拟"文件夹"查询
     * 
     * 示例：
     * - prefix = "images/" : 列出所有在 images "文件夹"下的文件
     * - prefix = "2024/01/" : 列出 2024/01 目录下的文件
     * 
     * @param bucketName 桶名称
     * @param prefix 前缀过滤
     * @return 对象名称列表
     */
    public List<String> listObjects(String bucketName, String prefix) {
        List<String> objectNames = new ArrayList<>();
        
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .build()
            );
            
            logger.info("\n📄 桶 '{}' 中的对象（前缀: '{}'）：", bucketName, prefix);
            for (Result<Item> result : results) {
                Item item = result.get();
                objectNames.add(item.objectName());
                logger.info("  - {} ({} 字节)", item.objectName(), item.size());
            }
            
        } catch (Exception e) {
            logger.error("❌ 列出对象失败: {}", e.getMessage());
        }
        
        return objectNames;
    }
    
    /**
     * 删除对象
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 是否删除成功
     * 
     * 注意：
     * - 删除操作不可恢复
     * - 删除不存在的对象不会报错
     */
    public boolean deleteObject(String bucketName, String objectName) {
        try {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            
            logger.info("✅ 对象 '{}' 删除成功！", objectName);
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 删除对象失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 检查对象是否存在
     * 
     * 使用场景：
     * - 上传前检查是否重复
     * - 下载前检查是否存在
     * - 避免不必要的操作
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 是否存在
     */
    public boolean objectExists(String bucketName, String objectName) {
        try {
            minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    
    // ==================== 高级功能 ====================
    
    /**
     * 生成预签名 URL（临时访问链接）
     * 
     * 这是什么？
     * - 生成一个临时的、可直接访问的 URL
     * - URL 会在指定时间后失效
     * - 不需要认证就可以访问
     * 
     * 使用场景：
     * 1. 文件分享：生成下载链接发给他人
     * 2. 前端上传：前端获取上传 URL，直接上传到 MinIO
     * 3. 临时公开：临时公开私有文件
     * 4. CDN 集成：作为 CDN 的源站
     * 
     * 工作原理：
     * - URL 中包含签名信息
     * - MinIO 验证签名和过期时间
     * - 过期后 URL 自动失效
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param expirySeconds 过期时间（秒），默认 7 天
     * @return URL 字符串，失败返回 null
     */
    public String generatePresignedUrl(String bucketName, String objectName, int expirySeconds) {
        try {
            String url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expirySeconds, TimeUnit.SECONDS)
                            .build()
            );
            
            logger.info("✅ 预签名 URL 生成成功！");
            logger.info("   有效期: {} 秒 ({} 小时)", expirySeconds, expirySeconds / 3600.0);
            logger.info("   URL: {}", url);
            return url;
            
        } catch (Exception e) {
            logger.error("❌ 生成预签名 URL 失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 生成预签名 URL（默认 1 小时有效）
     */
    public String generatePresignedUrl(String bucketName, String objectName) {
        return generatePresignedUrl(bucketName, objectName, 3600); // 1 小时
    }
    
    /**
     * 获取对象信息（元数据）
     * 
     * 可以获取什么信息？
     * - 文件大小
     * - 上传时间
     * - 内容类型
     * - ETag（文件唯一标识，类似 MD5）
     * - 自定义元数据
     * 
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return 对象信息字符串
     */
    public String getObjectInfo(String bucketName, String objectName) {
        try {
            var stat = minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()
            );
            
            StringBuilder info = new StringBuilder();
            info.append("\n📊 对象信息: ").append(objectName).append("\n");
            info.append("   大小: ").append(stat.size()).append(" 字节\n");
            info.append("   最后修改: ").append(stat.lastModified()).append("\n");
            info.append("   类型: ").append(stat.contentType()).append("\n");
            info.append("   ETag: ").append(stat.etag()).append("\n");
            
            logger.info(info.toString());
            return info.toString();
            
        } catch (Exception e) {
            logger.error("❌ 获取对象信息失败: {}", e.getMessage());
            return null;
        }
    }
    
    /**
     * 复制对象
     * 
     * 使用场景：
     * - 文件备份
     * - 跨桶移动文件
     * - 重命名文件（复制后删除原文件）
     * - 文件归档
     * 
     * @param sourceBucket 源桶名称
     * @param sourceObject 源对象名称
     * @param destBucket 目标桶名称
     * @param destObject 目标对象名称
     * @return 是否复制成功
     */
    public boolean copyObject(String sourceBucket, String sourceObject,
                             String destBucket, String destObject) {
        try {
            minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .bucket(destBucket)
                            .object(destObject)
                            .source(
                                    CopySource.builder()
                                            .bucket(sourceBucket)
                                            .object(sourceObject)
                                            .build()
                            )
                            .build()
            );
            
            logger.info("✅ 对象复制成功！");
            logger.info("   从: {}/{}", sourceBucket, sourceObject);
            logger.info("   到: {}/{}", destBucket, destObject);
            return true;
            
        } catch (Exception e) {
            logger.error("❌ 复制对象失败: {}", e.getMessage());
            return false;
        }
    }
    
    /**
     * 获取原生 MinIO 客户端
     * 
     * 为什么需要这个？
     * - 有些高级功能封装类可能没有提供
     * - 可以直接使用 MinIO SDK 的所有功能
     * 
     * @return MinIO 客户端实例
     */
    public MinioClient getClient() {
        return minioClient;
    }
}

